#include <string.h>

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pwd.h>
#include <syslog.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <X11/extensions/scrnsaver.h>
#include <X11/Intrinsic.h>
#include <X11/Shell.h>

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#define MIN_TSTATE 7
#define MAX_TSTATE 0

int throt (const char* cpu, int state) {
  int f;
  char* buf;
  buf = malloc(sizeof(int)+2);

  sprintf(buf, "%i\n", state);
  f = open(cpu, O_RDWR);
  if (errno) {
    syslog(LOG_ERR, "%i: Error opening %s: %s\n", __LINE__,
            cpu, strerror(errno));
    exit(EXIT_FAILURE);
  }
  write(f, buf, strlen(buf));
  if (errno) {
    syslog(LOG_ERR, "%i: Error writing %s: %s\n", __LINE__,
            cpu, strerror(errno));
    close(f);
    exit(EXIT_FAILURE);
  }
  close(f);

  free(buf);

  return(EXIT_SUCCESS);
}

int uppriv (uid_t *euid) {
  if (*euid) {
    if (! seteuid(0)) {
      /* printf("New EUID: %i\n", geteuid()); */
    } else {
      syslog(LOG_ERR, "%i: Error changing EUID to '(%i)': %s\n", __LINE__,
              0, strerror(errno));
      exit(EXIT_FAILURE);
    }
  }
  return(EXIT_SUCCESS);
}

int dnpriv (uid_t *euid) {
  if (! seteuid(*euid)) {
    /* printf("New EUID: %i\n", geteuid()); */
  } else {
    syslog(LOG_ERR, "%i: Error changing EUID to '(%i)': %s\n", __LINE__,
            *euid, strerror(errno));
    exit(EXIT_FAILURE);
  }
  return(EXIT_SUCCESS);
}

void daemonize(void) {
  int fd, f;
  const char* pidfile = "/var/run/xssthrottle.pid";

  f = fork();
  switch (f) {
  case 0:
    break;
  case -1:
    // Error
    syslog(LOG_ERR, "%i: Error daemonizing: %s\n", __LINE__,
            strerror(errno));
    exit(-1);
    break;
  default:
    _exit(0);
  }

  if (setsid() < 0) {
    syslog(LOG_ERR, "%i: Error daemonizing: %s\n", __LINE__,
            strerror(errno));
    exit(-1);
  }
  
  f = fork();
  switch (f) {
  case 0:
    break;
  case -1:
    // Error
    syslog(LOG_ERR, "%i: Error daemonizing: %s\n", __LINE__,
            strerror(errno));
    exit(-1);
    break;
  default:
    fd = open(pidfile, O_WRONLY|O_CREAT, 00644);
    if (fd > -1) {
      char* buf = malloc(sizeof(pid_t)+2);
      sprintf(buf, "%i\n", f);
      write(fd, buf, strlen(buf));
      close(fd);
      free(buf);
    } else {
      syslog(LOG_WARNING, "Error writing pid to %s: %s", pidfile, strerror(errno));
    }
    _exit(0);
  }

  chdir("/");

  fd = open("/dev/null", O_RDONLY);
  if (fd != 0) {
    dup2(fd, 0);
    close(fd);
  }
  fd = open("/dev/null", O_WRONLY);
  if (fd != 1) {
    dup2(fd, 1);
    close(fd);
  }
  fd = open("/dev/null", O_WRONLY);
  if (fd != 2) {
    dup2(fd, 2);
    close(fd);
  }

}


int main ( int argc, char **argv ) {
  static Display* dpy;
  static XScreenSaverInfo* xinfo;
  xinfo = malloc(sizeof(XScreenSaverInfo));
  XScreenSaverNotifyEvent* sevent;
  sevent = malloc(sizeof(XScreenSaverNotifyEvent));
  static XtAppContext App;
  XEvent event;
  Widget tl;
  int SS_event = 0;
  int SS_error = 0;

  extern int errno;
  const char* cpu0 = "/proc/acpi/processor/CPU0/throttling";
  uid_t oldeuid = geteuid();  

  int i;
  bool debug = false;

  /* do arguments */
  while ((i = getopt(argc, argv, "d")) > 0) {
    switch (i) {
    case 'd':
      debug = true;
      break;
    default:
      printf("You big dummy!\n");
      exit(1);
    }
  }

  if (! debug) {
    openlog(basename(argv[0]), LOG_PID, LOG_DAEMON);
    syslog(LOG_NOTICE, "Throttling daemon started\n");
  } else {
    openlog(basename(argv[0]), LOG_PID | LOG_PERROR, LOG_DAEMON);
    syslog(LOG_NOTICE, "Throttling daemon started in debug mode\n");
  } 

  /* daemonize */
  if (! debug)
    daemonize();

  /* renice to lowest priority */
  setpriority(PRIO_PROCESS, getpid(), 19);

  /* check for read access to cp0. if this fails we're prolly not on Linux */
  if (access(cpu0, R_OK)) {
    syslog(LOG_ERR, "%i: Error checking %s for read access: %s\n", __LINE__,
            cpu0, strerror(errno));
    exit(EXIT_FAILURE);
  }

  /* initialize the Xt parts of the app */
  tl = XtAppInitialize(&App, "xss", NULL, 0, &argc, argv, NULL, NULL, 0);
  dpy = XtDisplay(tl);

  /* initialize the "screen saver" */
  if (! XScreenSaverQueryExtension(dpy, &SS_event, &SS_error)) {
    syslog(LOG_ERR, "%i: X screen saver extension not found\n", __LINE__);
    exit(EXIT_FAILURE);
  }

  xinfo = XScreenSaverAllocInfo();
  XScreenSaverQueryInfo(dpy, RootWindow(dpy, DefaultScreen(dpy)), xinfo);
  XScreenSaverSelectInput(dpy, RootWindow(dpy, DefaultScreen(dpy)), ScreenSaverNotifyMask);
  free(xinfo);

  /* use this as our Xt event loop */
  for (;;) {
    if (debug)
      syslog(LOG_DEBUG, "Waiting for Xt event...\n"); 
    XtAppNextEvent(App, &event);
    if (debug)
      syslog(LOG_DEBUG, "Recieved Xt event #%i...\n", event.type);
    if (event.type == SS_event) {
      if (debug)
        syslog(LOG_DEBUG, "  ScreenSaver event\n");
      sevent = (XScreenSaverNotifyEvent *) &event;
      if (debug)
        syslog(LOG_DEBUG, "  ScreenSaver state: ");
      switch (sevent->state) {
      case ScreenSaverOn:
        if (debug)
          syslog(LOG_DEBUG, "    On: %i", sevent->state);
        oldeuid = geteuid();
        uppriv(&oldeuid);
        throt(cpu0, MIN_TSTATE);
        dnpriv(&oldeuid);
        break;
      case ScreenSaverOff:
        if (debug)
          syslog(LOG_DEBUG, "    Off: %i", sevent->state);
        oldeuid = geteuid();
        uppriv(&oldeuid);
        throt(cpu0, MAX_TSTATE);
        dnpriv(&oldeuid);
        break;
      case ScreenSaverDisabled:
        if (debug)
          syslog(LOG_DEBUG, "    Disabled: %i", sevent->state);
        break;
      default:
        if (debug)
          syslog(LOG_DEBUG, "    Unknown: %i", sevent->state);
      }
      switch (sevent->kind) {
        if (debug)
          syslog(LOG_DEBUG, "  ScreenSaver kind: ");
      case ScreenSaverBlanked:
        if (debug)
          syslog(LOG_DEBUG, "    Blanked: %i", sevent->state);
        break;
      case ScreenSaverInternal:
        if (debug)
          syslog(LOG_DEBUG, "    Internal: %i", sevent->state);
        break;
      case ScreenSaverExternal:
        if (debug)
          syslog(LOG_DEBUG, "    External: %i", sevent->state);
        break;
      default:
        if (debug)
          syslog(LOG_DEBUG, "    Unknown: %i", sevent->state);
      }
    } else {
      XtDispatchEvent(&event);
    }
  }
             
  free(sevent);
  closelog();
  exit(EXIT_SUCCESS);
}
